package edu.mcw.rgd.clinminer.umls.domain;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.Stack;

import javax.persistence.Column;
import javax.persistence.Id;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.TypedQuery;

import org.eclipse.persistence.annotations.ReadOnly;
import org.springframework.roo.addon.javabean.RooJavaBean;
import org.springframework.roo.addon.jpa.activerecord.RooJpaActiveRecord;
import org.springframework.roo.addon.tostring.RooToString;

import edu.mcw.rgd.clinminer.data.ClinminerException;
import edu.mcw.rgd.clinminer.umls.type.AUI;
import edu.mcw.rgd.clinminer.umls.type.CUI;

@RooJavaBean
@RooToString
@RooJpaActiveRecord(persistenceUnit = "persistenceUnitUMLS", versionColumn = "")
@ReadOnly
// see
// http://www.ncbi.nlm.nih.gov/books/NBK9685/table/ch03.T.computable_hierarchies_file__mrhi/?report=objectonly
@NamedQueries({
		@NamedQuery(name = "MrHier.findParents", query = "SELECT DISTINCT H.paui FROM MrHier H WHERE H.aui=:aui"),
		@NamedQuery(name = "MrHier.findChildren", query = "SELECT DISTINCT H.aui FROM MrHier H WHERE H.paui=:aui"),
		@NamedQuery(name = "MrHier.hasChildren", query = "SELECT H.aui FROM MrHier H WHERE H.paui=:aui"),
		@NamedQuery(name = "findUniqueSources", query = "SELECT DISTINCT H.sab FROM MrHier H"),
		@NamedQuery(name = "findAllAUIbyCUIandSAB", query = "SELECT h.aui FROM MrHier h, MrConso c WHERE h.cui=:cui AND h.sab=:sab AND c.aui=h.aui"),
		@NamedQuery(name = "findActiveAUIbyCUIandSAB", query = "SELECT h.aui FROM MrHier h, MrConso c WHERE h.cui=:cui AND h.sab=:sab AND c.suppress='N' AND c.aui=h.aui"), })
public class MrHier {
	@Id
	@Column(name = "AUI")
	private String aui;

	@Column(name = "CXN")
	private Integer cxn;

	@Column(name = "PAUI")
	private String paui;

	@Column(name = "CUI")
	private String cui;

	@Column(name = "SAB")
	private String sab;

	@Column(name = "RELA")
	private String rela;

	@Column(name = "PTR")
	private String ptr;

	@Column(name = "HCD")
	private String hcd;

	@Column(name = "CVF")
	private Integer cvf;

	public static List<AUI> getParentAUIs(AUI nodeAui, Integer limit)
			throws ClinminerException {
		List<AUI> result = new ArrayList<AUI>();
		TypedQuery<String> q = entityManager().createNamedQuery(
				"MrHier.findParents", String.class).setParameter("aui",
				nodeAui.toString());
		if (limit != -1) {
			q.setMaxResults(limit);
		}

		for (String sAui : q.getResultList()) {
			// the little oddity of UMLS
			if (sAui == null) {
				continue;
			}
			result.add(new AUI(sAui));
		}
		return result;
	}

	// no limit
	public static List<AUI> getParentAUIs(AUI nodeAui)
			throws ClinminerException {
		return getParentAUIs(nodeAui, -1);
	}

	public static List<AUI> getChildAUIs(AUI nodeAui, Integer limit)
			throws ClinminerException {
		List<AUI> result = new ArrayList<AUI>();
		TypedQuery<String> q = entityManager().createNamedQuery(
				"MrHier.findChildren", String.class).setParameter("aui",
				nodeAui.toString());
		if (limit != -1) {
			q.setMaxResults(limit);
		}
		for (String sAui : q.getResultList()) {
			result.add(new AUI(sAui));
		}
		return result;
	}

	public static List<AUI> getChildAUIs(AUI nodeAui) throws ClinminerException {
		return getChildAUIs(nodeAui, -1);
	}

	public static Set<AUI> getSiblingAUIs(AUI aui, Integer limit)
			throws ClinminerException {
		Set<AUI> result = new HashSet<AUI>();
		// for root terms there are no parents
		result.add(aui);
		for (AUI parent : getParentAUIs(aui, limit)) {
			result.addAll(getChildAUIs(parent, limit));
		}
		return result;
	}

	public static Set<AUI> getSiblingAUIs(AUI aui) throws ClinminerException {
		return getSiblingAUIs(aui, -1);
	}

	public static Set<AUI> getAllParentAUIs(AUI aui) throws ClinminerException {
		Set<AUI> result = new HashSet<AUI>();
		Stack<AUI> stack = new Stack<AUI>();
		// seed the stack
		stack.add(aui);

		while (stack.size() != 0) {
			if (result.contains(stack.peek())) {
				stack.pop();
				continue;
			}
			result.add(stack.peek());
			// add to process stack

			stack.addAll(MrHier.getParentAUIs(stack.pop()));
		}
		result.remove(aui);
		return result;
	}

	public static Boolean hasChildren(String aui) {
		List<String> result = entityManager()
				.createNamedQuery("MrHier.hasChildren", String.class)
				.setParameter("aui", aui).setMaxResults(1).getResultList();
		if (result.isEmpty()) {
			return false;
		} else {
			return true;
		}
	}

	public static Collection<String> findDistinctSources() {
		List<String> result = entityManager().createNamedQuery(
				"findUniqueSources", String.class).getResultList();
		if (result.get(0) == null) {
			return Collections.emptyList();
		}
		return result;
	}

	public static Collection<AUI> findAUIbyCUIandSAB(CUI cui, String sab,
			Boolean includeSuppressed) throws ClinminerException {
		String queryName;
		if (includeSuppressed) {
			queryName = "findAllAUIbyCUIandSAB";
		} else {
			queryName = "findActiveAUIbyCUIandSAB";
		}
		Collection<AUI> result = new HashSet<AUI>();
		for (String aui : entityManager()
				.createNamedQuery(queryName, String.class)
				.setParameter("cui", cui.toString()).setParameter("sab", sab)
				.getResultList()) {
			result.add(new AUI(aui));

		}
		return result;
	}
}